
/*    This file is part of the Managarm operating system.
 *   Copyright (C) 2007, 2008, 2009  Alexander van der Grinten
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

package org.managarm.jmanacclib.generic;

import java.util.ArrayList;

import org.managarm.jmanacclib.generic.GnMthCompiler;
import org.managarm.jmanavalib.core.VaDatatype;
import org.managarm.jmanavalib.core.VaFrame;
import org.managarm.jmanavalib.core.VaInst;
import org.managarm.jmanavalib.core.VaProcedure;

public class GnProcCompiler {
	// represents our register stack
	private final class GnRegStack {
		// maps generic registers to virtual registers
		private final int[] registerMap;
		
		// stores the type of the virtual registers
		private final ArrayList<VaDatatype> registerTypes
				= new ArrayList<VaDatatype>();
		
		// construct a register stack for "regnum" generic registers
		private GnRegStack() {
			registerMap = new int[methodCompiler.method.registers.size()];
		}
		
		// returns the virtual register that
		// stores the generic register "register"
		private final int getRegister(final int register) {
			if(register == 0)
				return 0;
			return registerMap[register - 1];
		}
		
		// pushs generic register "register" onto the stack
		private final void pushRegister(final int register) {
			// insert the register into the register array
			final int destregister = registerTypes.size() + 1;
			registerTypes.add(methodCompiler.method.registers.get(register - 1));
			
			// update the generic -> virtual map
			registerMap[register - 1] = registerTypes.size();
			// insert pass instructions
			if(catcher != null && catcher.argSet.contains(register))
				instructions.add(new VaInst.InstPass
						(catcher.argList.indexOf(register) + 1, destregister));
		}
		
		// map generic register "register" to virtual register "alias"
		private final void aliasRegister(final int register, final int alias) {
			registerMap[register - 1] = alias;
		}
	}
	
	// the method compiler we belong to
	private final GnMthCompiler methodCompiler;
	// stores the procedure we have to compile
	private final GnProcInfo procedure;
	// exception handler frame of this procedure
	private final GnFrameInfo catcher;
	// stores the instructions of the current frame
	private ArrayList<VaInst> instructions;
	
	public GnProcCompiler(final GnMthCompiler methodcompiler,
			final GnProcInfo procedure, final GnFrameInfo catcher) {
		this.methodCompiler = methodcompiler;
		this.procedure = procedure;
		this.catcher = catcher;
	}
	
	public final VaProcedure compile() {
		// compile each frame
		final VaFrame[] frames = new VaFrame[procedure.frameInfos.length];
		for(int i = 0; i < procedure.frameInfos.length; i++)
			frames[i] = compileFrame(procedure, procedure.frameInfos[i]);
		
		return new VaProcedure(frames);
	}
	
	// compiles a frame
	private final VaFrame compileFrame(final GnProcInfo procedure,
			final GnFrameInfo frame) {
		instructions = new ArrayList<VaInst>();
		// create a register stack
		final GnRegStack registers = new GnRegStack();
		
		// push the arguments onto the register stack
		for(int i = 0; i < frame.argList.size(); i++)
			registers.pushRegister(frame.argList.get(i));
		
		// compile each instruction
		for(int i = 0; i < frame.frameObject.instructions.size(); i++) {
			final GnInst opcode = frame.frameObject.instructions.get(i);
			
			if(opcode instanceof GnInst.InstLoad) {
				final GnInst.InstLoad inst = (GnInst.InstLoad)opcode;
				instructions.add(new VaInst.InstLoad(inst.value));
				registers.pushRegister(inst.register);
			}else if(opcode instanceof GnInst.InstReturn) {
				final GnInst.InstReturn inst = (GnInst.InstReturn)opcode;
				instructions.add(new VaInst.InstReturn
						(registers.getRegister(inst.register)));
			}else if(opcode instanceof GnInst.InstBinOperation) {
				final GnInst.InstBinOperation inst
						= (GnInst.InstBinOperation)opcode;
				if(inst.operator == GnInst.InstBinOperation.OP_ARITH_ADD) {
					instructions.add(new VaInst.InstBinOperation
							(VaInst.InstBinOperation.OP_ARITH_ADD,
								registers.getRegister(inst.left),
								registers.getRegister(inst.right)));
				}else if(inst.operator == GnInst.InstBinOperation.OP_ARITH_SUB) {
					instructions.add(new VaInst.InstBinOperation
							(VaInst.InstBinOperation.OP_ARITH_SUB,
								registers.getRegister(inst.left),
								registers.getRegister(inst.right)));
				}else if(inst.operator == GnInst.InstBinOperation.OP_ARITH_MUL) {
					instructions.add(new VaInst.InstBinOperation
							(VaInst.InstBinOperation.OP_ARITH_MUL,
								registers.getRegister(inst.left),
								registers.getRegister(inst.right)));
				}else if(inst.operator == GnInst.InstBinOperation.OP_ARITH_DIV) {
					instructions.add(new VaInst.InstBinOperation
							(VaInst.InstBinOperation.OP_ARITH_DIV,
								registers.getRegister(inst.left),
								registers.getRegister(inst.right)));
				}else if(inst.operator == GnInst.InstBinOperation.OP_ARITH_MOD) {
					instructions.add(new VaInst.InstBinOperation
							(VaInst.InstBinOperation.OP_ARITH_MOD,
								registers.getRegister(inst.left),
								registers.getRegister(inst.right)));
				}else if(inst.operator == GnInst.InstBinOperation.OP_ARITH_OR) {
					instructions.add(new VaInst.InstBinOperation
							(VaInst.InstBinOperation.OP_ARITH_OR,
								registers.getRegister(inst.left),
								registers.getRegister(inst.right)));
				}else if(inst.operator == GnInst.InstBinOperation.OP_ARITH_AND) {
					instructions.add(new VaInst.InstBinOperation
							(VaInst.InstBinOperation.OP_ARITH_AND,
								registers.getRegister(inst.left),
								registers.getRegister(inst.right)));
				}else if(inst.operator == GnInst.InstBinOperation.OP_ARITH_XOR) {
					instructions.add(new VaInst.InstBinOperation
							(VaInst.InstBinOperation.OP_ARITH_XOR,
								registers.getRegister(inst.left),
								registers.getRegister(inst.right)));
				}else if(inst.operator == GnInst.InstBinOperation.OP_ARITH_SHL) {
					instructions.add(new VaInst.InstBinOperation
							(VaInst.InstBinOperation.OP_ARITH_SHL,
								registers.getRegister(inst.left),
								registers.getRegister(inst.right)));
				}else if(inst.operator == GnInst.InstBinOperation.OP_ARITH_SHR) {
					instructions.add(new VaInst.InstBinOperation
							(VaInst.InstBinOperation.OP_ARITH_SHR,
								registers.getRegister(inst.left),
								registers.getRegister(inst.right)));
				}else if(inst.operator == GnInst.InstBinOperation.OP_SETIF_EQUALS) {
					instructions.add(new VaInst.InstBinOperation
							(VaInst.InstBinOperation.OP_SETIF_EQUALS,
								registers.getRegister(inst.left),
								registers.getRegister(inst.right)));
				}else if(inst.operator == GnInst.InstBinOperation.OP_SETIF_ABOVE) {
					instructions.add(new VaInst.InstBinOperation
							(VaInst.InstBinOperation.OP_SETIF_ABOVE,
								registers.getRegister(inst.left),
								registers.getRegister(inst.right)));
				}else if(inst.operator == GnInst.InstBinOperation.OP_SETIF_BELOW) {
					instructions.add(new VaInst.InstBinOperation
							(VaInst.InstBinOperation.OP_SETIF_BELOW,
								registers.getRegister(inst.left),
								registers.getRegister(inst.right)));
				}else{
					System.err.println("compileFrame(): Invalid operator " +
							inst.operator + " for " +
							"GnInst.InstBinOperation");
					System.exit(1);
				}
				registers.pushRegister(inst.register);
			}else if(opcode instanceof GnInst.InstUnOperation) {
				final GnInst.InstUnOperation inst
						= (GnInst.InstUnOperation)opcode;
				if(inst.operator == GnInst.InstUnOperation.OP_ARITH_NEG) {
					instructions.add(new VaInst.InstUnOperation
							(VaInst.InstUnOperation.OP_ARITH_NEG,
								registers.getRegister(inst.operand)));
				}else if(inst.operator == GnInst.InstUnOperation.OP_ARITH_NOT) {
					instructions.add(new VaInst.InstUnOperation
							(VaInst.InstUnOperation.OP_ARITH_NOT,
								registers.getRegister(inst.operand)));
				}else if(inst.operator == GnInst.InstUnOperation.OP_ARR_SIZE) {
					instructions.add(new VaInst.InstUnOperation
							(VaInst.InstUnOperation.OP_ARR_SIZE,
								registers.getRegister(inst.operand)));
				}else{
					System.err.println("compileFrame(): Invalid operator " +
							inst.operator + " for " +
							"GnInst.InstUnOperation");
					System.exit(1);
				}
				registers.pushRegister(inst.register);
			}else if(opcode instanceof GnInst.InstClone) {
				final GnInst.InstClone inst = (GnInst.InstClone)opcode;
				registers.aliasRegister(inst.register,
						registers.getRegister(inst.operand));
			}else if(opcode instanceof GnInst.InstJump) {
				final GnInst.InstJump inst = (GnInst.InstJump)opcode;
				
				// determine the jump arguments
				final GnFrameInfo target = procedure.frameInfos[inst.frameId];
				final int[] arguments = new int[target.argSet.size()];
				for(int j = 0; j < target.argList.size(); j++)
					arguments[j] = registers.getRegister(target.argList.get(j));
				
				instructions.add(new VaInst.InstJump(inst.frameId,
						registers.getRegister(inst.register), arguments));
			}else if(opcode instanceof GnInst.InstInvoke) {
				final GnInst.InstInvoke inst = (GnInst.InstInvoke)opcode;
				
				// determine the invokation arguments
				final int[] arguments = new int[inst.getArgumentCnt()];
				for(int j = 0; j < inst.getArgumentCnt(); j++)
					arguments[j] = registers.getRegister(inst.getArgument(j));
				
				instructions.add(new VaInst.InstInvoke(inst.mthInfo,
						registers.getRegister(inst.object),
						inst.virtual, inst.result != 0 ? true : false,
						arguments));
				if(inst.result != 0)
					registers.pushRegister(inst.result);
			}else if(opcode instanceof GnInst.InstCreateObject) {
				final GnInst.InstCreateObject inst
						= (GnInst.InstCreateObject)opcode;
				instructions.add(new VaInst.InstCreateObject(inst.datatype));
				registers.pushRegister(inst.register);
			}else if(opcode instanceof GnInst.InstCreateArray) {
				final GnInst.InstCreateArray inst
						= (GnInst.InstCreateArray)opcode;
				instructions.add(new VaInst.InstCreateArray(inst.datatype,
						registers.getRegister(inst.size)));
				registers.pushRegister(inst.register);
			}else if(opcode instanceof GnInst.InstArrAccess) {
				final GnInst.InstArrAccess inst
						= (GnInst.InstArrAccess)opcode;
				if(inst.operator == GnInst.InstArrAccess.OP_LOAD) {
					instructions.add(new VaInst.InstArrAccess
							(VaInst.InstArrAccess.OP_LOAD,
							registers.getRegister(inst.array),
							registers.getRegister(inst.index), 0));
					registers.pushRegister(inst.operand);
				}else if(inst.operator == GnInst.InstArrAccess.OP_STORE) {
					instructions.add(new VaInst.InstArrAccess
							(VaInst.InstArrAccess.OP_STORE,
							registers.getRegister(inst.array),
							registers.getRegister(inst.index),
							registers.getRegister(inst.operand)));
				}else{
					System.err.println("compileFrame(): Invalid operator");
					System.exit(1);
				}
			}else if(opcode instanceof GnInst.InstFldAccess) {
				final GnInst.InstFldAccess inst
						= (GnInst.InstFldAccess)opcode;
				if(inst.operator == GnInst.InstFldAccess.OP_LOAD) {
					instructions.add(new VaInst.InstFldAccess
							(VaInst.InstFldAccess.OP_LOAD, inst.fldInfo,
							registers.getRegister(inst.object), 0));
					registers.pushRegister(inst.operand);
				}else if(inst.operator == GnInst.InstFldAccess.OP_STORE) {
					instructions.add(new VaInst.InstFldAccess
							(VaInst.InstFldAccess.OP_STORE, inst.fldInfo,
							registers.getRegister(inst.object),
							registers.getRegister(inst.operand)));
				}else{
					System.err.println("compileFrame(): Invalid operator");
					System.exit(1);
				}
			}else if(opcode instanceof GnInst.InstCast) {
				final GnInst.InstCast inst = (GnInst.InstCast)opcode;
				instructions.add(new VaInst.InstCast(inst.datatype,
						registers.getRegister(inst.source)));
				registers.pushRegister(inst.dest);
			}else if(opcode instanceof GnInst.InstInstanceof) {
				final GnInst.InstInstanceof inst = (GnInst.InstInstanceof)opcode;
				instructions.add(new VaInst.InstInstanceof(inst.datatype,
						registers.getRegister(inst.object)));
				registers.pushRegister(inst.register);
			}else if(opcode instanceof GnInst.InstDlgConstruct) {
				final GnInst.InstDlgConstruct inst
						= (GnInst.InstDlgConstruct)opcode;
				instructions.add(new VaInst.InstDlgConstruct(inst.method,
						registers.getRegister(inst.object)));
				registers.pushRegister(inst.result);
			}else if(opcode instanceof GnInst.InstCatch) {
				final GnInst.InstCatch inst = (GnInst.InstCatch)opcode;
				
				// determine the arguments
				final GnProcInfo procinfo
						= methodCompiler.procedures.get(inst.procedure);
				final GnFrameInfo target = procinfo.frameInfos[0];
				final int[] arguments = new int[target.argSet.size()];
				for(int j = 0; j < target.argList.size(); j++)
					arguments[j] = registers.getRegister(target.argList.get(j));
				
				final GnProcCompiler compiler = new GnProcCompiler
						(methodCompiler, procinfo,
								procedure.frameInfos[inst.frame]);
				instructions.add(new VaInst.InstCatch(inst.frame, arguments,
						compiler.compile()));
			}else if(opcode instanceof GnInst.InstThrow) {
				final GnInst.InstThrow inst = (GnInst.InstThrow)opcode;
				instructions.add(new VaInst.InstThrow
						(registers.getRegister(inst.exception)));
			}else if(opcode instanceof GnInst.InstLeave) {
				instructions.add(new VaInst.InstLeave());
			}else if(opcode instanceof GnInst.InstXNatMthAddress) {
				final GnInst.InstXNatMthAddress inst
						= (GnInst.InstXNatMthAddress)opcode;
				instructions.add(new VaInst.InstXNatMthAddress(inst.mthInfo));
				registers.pushRegister(inst.dest);
			}else if(opcode instanceof GnInst.InstXNatSizeofObject) {
				final GnInst.InstXNatSizeofObject inst
						= (GnInst.InstXNatSizeofObject)opcode;
				instructions.add(new VaInst.InstXNatSizeofObject(inst.datatype));
				registers.pushRegister(inst.dest);
			}else if(opcode instanceof GnInst.InstXNatAddressCast) {
				final GnInst.InstXNatAddressCast inst
						= (GnInst.InstXNatAddressCast)opcode;
				instructions.add(new VaInst.InstXNatAddressCast(inst.datatype,
						registers.getRegister(inst.source)));
				registers.pushRegister(inst.dest);
			}else if(opcode instanceof GnInst.InstXNatAddressOf) {
				final GnInst.InstXNatAddressOf inst
						= (GnInst.InstXNatAddressOf)opcode;
				if(inst.address == GnInst.InstXNatAddressOf.ADDRESS_OBJECT) {
					instructions.add(new VaInst.InstXNatAddressOf
							(VaInst.InstXNatAddressOf.ADDRESS_OBJECT,
									registers.getRegister(inst.object)));
				}else if(inst.address == GnInst.InstXNatAddressOf.ADDRESS_DLG_OBJECT) {
					instructions.add(new VaInst.InstXNatAddressOf
							(VaInst.InstXNatAddressOf.ADDRESS_DLG_OBJECT,
									registers.getRegister(inst.object)));
				}else if(inst.address == GnInst.InstXNatAddressOf.ADDRESS_DLG_METHOD) {
					instructions.add(new VaInst.InstXNatAddressOf
							(VaInst.InstXNatAddressOf.ADDRESS_DLG_METHOD,
									registers.getRegister(inst.object)));
				}else{
					System.err.println("AddressOf: Invalid address");
					System.exit(1);
				}
				registers.pushRegister(inst.result);
			}else if(opcode instanceof GnInst.InstXNatMakeDelegate) {
				final GnInst.InstXNatMakeDelegate inst
						= (GnInst.InstXNatMakeDelegate)opcode;
				instructions.add(new VaInst.InstXNatMakeDelegate(inst.datatype,
						registers.getRegister(inst.method),
						registers.getRegister(inst.object)));
				registers.pushRegister(inst.result);
			}else if(opcode instanceof GnInst.InstXNatRefOperation) {
				final GnInst.InstXNatRefOperation inst
						= (GnInst.InstXNatRefOperation)opcode;
				instructions.add(new VaInst.InstXNatRefOperation
						(inst.operation, registers.getRegister(inst.register)));
			}else if(opcode instanceof GnInst.InstXAtomCas) {
				final GnInst.InstXAtomCas inst
						= (GnInst.InstXAtomCas)opcode;
				if(inst.dest == 0) {
					instructions.add(new VaInst.InstXAtomCas(inst.field,
							registers.getRegister(inst.object),
							registers.getRegister(inst.source),
							registers.getRegister(inst.value), false));
				}else{
					instructions.add(new VaInst.InstXAtomCas(inst.field,
							registers.getRegister(inst.object),
							registers.getRegister(inst.source),
							registers.getRegister(inst.value), true));
					registers.pushRegister(inst.dest);
				}
			}else if(opcode instanceof GnInst.InstXPerfArrayCopy) {
				final GnInst.InstXPerfArrayCopy inst
						= (GnInst.InstXPerfArrayCopy)opcode;
				instructions.add(new VaInst.InstXPerfArrayCopy
						(registers.getRegister(inst.dest),
						registers.getRegister(inst.source),
						registers.getRegister(inst.destOffset),
						registers.getRegister(inst.sourceOffset),
						registers.getRegister(inst.size)));
			}else{
				System.err.println("compileFrame(): Invalid instruction");
				System.exit(1);
			}
		}
		
		// create a register data type and an instruction array
		final int regcnt = registers.registerTypes.size();
		final int instcnt = instructions.size();
		final VaDatatype[] regarray = new VaDatatype[regcnt];
		final VaInst[] instarray = new VaInst[instcnt];
		for(int i = 0; i < regcnt; i++)
			regarray[i] = registers.registerTypes.get(i);
		for(int i = 0; i < instcnt; i++)
			instarray[i] = instructions.get(i);
		
		return new VaFrame(frame.argSet.size(), regarray, instarray);
	}
}
